package com.sunday.common.statemachine.study.e7_Join;

import com.sunday.common.statemachine.study.e8_History.Events;
import com.sunday.common.statemachine.study.e8_History.States3;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.config.model.StateMachineModel;
import org.springframework.statemachine.config.model.verifier.StateMachineModelVerifier;
import org.springframework.statemachine.listener.StateMachineListener;
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
import org.springframework.statemachine.state.State;

/**
 * Join 状态的作用和说明
 * join 是状态机中的一种机制，用于合并多个并行状态流，即当多个并行路径完成时，将它们合并到一个目标状态。这种方式常常与 fork 配合使用，fork 用于分发（将状态流分到多个并行路径），而 join 用于在并行路径完成后合并这些状态流。
 * <p>
 * Join 状态的工作原理
 * 多路径并行： 在状态机中，可以在某个点（例如 fork）将状态流分为多个并行路径，每个路径代表一个任务。每个任务有不同的目标状态。
 * <p>
 * 合并状态流： join 状态用于在多个并行路径完成后，将它们合并到一个单一的目标状态。join 的目标状态是“接收合并后状态的最终状态”，确保所有并行任务完成后，状态流可以继续。
 * <p>
 * 使用场景：
 * <p>
 * 并行任务：当你有多个并行的处理任务，所有任务完成后，你希望合并这些任务的结果并继续后续操作。
 * 控制流合并：在复杂的业务流程中，多个独立的处理流程可能在不同的路径上执行，当它们都完成时，控制流需要汇聚到一个状态继续执行。
 * join 与 guard 的结合： join 允许多个并行状态完成后合并为一个目标状态。你可以使用 guard 来确保在某些条件下才会发生状态转移。比如，只有当所有任务成功完成时，才能合并到下一个状态。
 * <p>
 * 总结：
 * join 用于将多个并行的状态流合并到一个状态，通常和 fork 配合使用，确保多个并行任务完成后，状态流可以继续。
 * guard 用于控制条件转移，确保只有在满足某些条件时，才会发生状态转移。
 * extendedState 提供了一个存储业务数据的地方，可以通过它来决定状态转移的逻辑。
 * 这个例子展示了如何使用 join 来汇聚多个并行的状态流，并结合 guard 来确保状态转移的条件。
 */

@Slf4j
@Configuration  // 声明该类为Spring的配置类
@EnableStateMachine  // 启用Spring状态机功能
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {

    @Override
    public void configure(StateMachineConfigurationConfigurer<OrderStates, OrderEvents> config) throws Exception {
        config
//                .withVerifier()
//                .enabled(true)
//                .verifier(new CustomStateMachineModelVerifier())
//                .and()

                .withConfiguration()
                .autoStartup(true)
                .listener(listener());
    }

    @Override
    public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) throws Exception {
        states
                .withStates()
                .initial(OrderStates.S1)
                .fork(OrderStates.S2)
                .state(OrderStates.S3)
                .join(OrderStates.S4)
                .state(OrderStates.S5)
                .and()

                .withStates()
                .parent(OrderStates.S3)
                .initial(OrderStates.S2I)
                .state(OrderStates.S21)
                .state(OrderStates.S22)
                .end(OrderStates.S2F)
                .and()

                .withStates()
                .parent(OrderStates.S3)
                .initial(OrderStates.S3I)
                .state(OrderStates.S31)
                .state(OrderStates.S32)
                .end(OrderStates.S3F)
        ;

    }

    @Override
    public void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions) throws Exception {
        transitions
                // 新建订单 -> 检查库存 触发条件 检查库存事件
                .withExternal()  // 配置外部状态转换（即不同状态之间的转换）
                .source(OrderStates.S1)
                .target(OrderStates.S2)
                .event(OrderEvents.TS2)
                .action(context -> {
                    log.info("TS2 : {}", context.getStateMachine().getState().getId());
                })
                .and()

                .withFork()
                .source(OrderStates.S2)
                .target(OrderStates.S21)
                .target(OrderStates.S31)
                .and()

                .withExternal()  // 配置外部状态转换（即不同状态之间的转换）
                .source(OrderStates.S21)
                .target(OrderStates.S22)
                .event(OrderEvents.TS22)
                .action(context -> {
                    log.info("TS22 triggered, transitioning from {} to {}", context.getSource().getId(), context.getTarget().getId());
                })
                .and()

                .withExternal()  // 配置外部状态转换（即不同状态之间的转换）
                .source(OrderStates.S31)
                .target(OrderStates.S32)
                .event(OrderEvents.TS32)
                .action(context -> {
                    log.info("TS32 triggered, transitioning from {} to {}", context.getSource().getId(), context.getTarget().getId());
                })
                .and()

                .withExternal()  // 配置外部状态转换（即不同状态之间的转换）
                .source(OrderStates.S22)
                .target(OrderStates.S2F)
                .event(OrderEvents.TS2F)
                .action(context -> {
                    log.info("TS2F triggered, transitioning from {} to {}", context.getSource().getId(), context.getTarget().getId());
                })
                .and()

                .withExternal()  // 配置外部状态转换（即不同状态之间的转换）
                .source(OrderStates.S32)
                .target(OrderStates.S3F)
                .event(OrderEvents.TS3F)
                .action(context -> {
                    log.info("TS3F triggered, transitioning from {} to {}", context.getSource().getId(), context.getTarget().getId());
                })
                .and()

                // 多个源状态汇聚到 S4（通过 join）
                .withJoin()
                .source(OrderStates.S2F)
                .source(OrderStates.S3F)
                .target(OrderStates.S4)
                .and()

                // 从 S4 到 S5 的普通状态转换，带 Guard 表达式
                .withExternal()
                .source(OrderStates.S4)
                .target(OrderStates.S5)
                .guard(context -> {
                    context.getExtendedState();
                    return true;
                })
//                .guardExpression("!extendedState.variables.isEmpty()")  // 只有 extendedState 中有数据时才转移
//                .guardExpression("true")
                .action(context -> {
                    log.info("Transitioning from {} to {} based on guard expression", context.getSource().getId(), context.getTarget().getId());
                })
                .and()

                // 当 extendedState 为空时，跳到终态
                .withExternal()
                .source(OrderStates.S4)
                .target(OrderStates.SF)
//                .guardExpression("extendedState.variables.isEmpty()") // extendedState 为空时转移到终态
                .action(context -> {
                    log.info("Transitioning from {} to {} based on guard expression", context.getSource().getId(), context.getTarget().getId());
                });
        ;

    }

    /**
     * 为什么触发两次 stateChanged()？
     * <p>
     * 因为状态机中存在层次结构，从 UNPAID 到 PAID 是一次状态变更，而从 PAID 自动切换到 PENDING_OUTBOUND 是另一层次变更。
     * 为什么 UNPAID to PAID 出现在 PENDING_OUTBOUND 后？
     * <p>
     * 日志记录的顺序是由状态进入的顺序决定的：先进入 PAID，再进入其子状态 PENDING_OUTBOUND。但 PENDING_OUTBOUND 是最终的细化状态，因此先记录。
     * 如何解决？
     * <p>
     * 在 stateChanged() 方法中通过 getParent() 方法提取父状态，记录完整的父子状态信息。
     * 也可以考虑使用 entry() 和 exit() 钩子方法进行状态进入和退出日志的更精确记录。
     */
    @Bean
    public StateMachineListener<OrderStates, OrderEvents> listener() {
        return new StateMachineListenerAdapter<>() {
            @Override
            public void stateChanged(State<OrderStates, OrderEvents> from, State<OrderStates, OrderEvents> to) {
                log.info("State change from {} to {}", from == null ? "无" : from.getId(), to.getId());
            }
        };
    }

}